-- 网关服务
-- Author: Colocasia
-- Date: 2019-06-11

local skynet = require "skynet";
require "skynet.manager";
local socket = require "us_socket";
local table = require "us.table";
local log = require "log";

local SEND_SESSION_NO_RET = 0

-- 配置
local gateconf =
{
    name = "gate",
    host = "0.0.0.0",
    port = tonumber(skynet.getenv "gate_port"),
}

-- gate 服务
local gate = {}
-- watchdog 服务
local watchdog
-- login 服务
local login

-- 数据结构
local connection = {}   -- fd -> connection : { fd, client, agent, ip, mode }
local forwarding = {}   -- agent -> connection

-- Socket
local socket_ipv4 = nil;
local socket_ipv6 = nil;

-- 最大连接数
local max_client = 1024;

-- 停止转发
local function unforward(c)
	if c.agent then
		forwarding[c.agent] = nil
		c.agent = nil
		c.client = nil
	end
end

-- 关闭连接
local function close_fd(fd)
	local c = connection[fd]
	if c then
		unforward(c);
        connection[fd] = nil;
        socket.close(fd);
        return true;
    end
    return false
end

---------- socket 处理 ----------
local SOCKET = {};
-- socket 一个客户端连接打开
local function connect(fd, addr)
    log.info('[gate] client connected, fd = %d, addr = %s', fd, addr);
    socket.nodelay(fd);
	local c = {
		fd = fd,
		ip = addr,
	}
	connection[fd] = c
    skynet.send(watchdog, "lua", "socket", "open", fd, addr);
end
SOCKET.on_connect = connect;

-- socket 一个客户端连接收到数据
local function message(fd, msg, sz)
    -- recv a package, forward it
    local c = connection[fd]
	local agent = c.agent
    if agent then
        log.debug('[gate] message redirect, agent = %s, client = %s, fd = %s, msg = %s, sz = %s', agent, c.client, fd, msg, sz);
		skynet.redirect(agent, c.client, "client", fd, msg, sz)
	else
		skynet.send(watchdog, "lua", "socket", "data", fd, skynet.tostring(msg, sz), sz)
		-- skynet.tostring will copy msg to a string, so we must free msg here.
		skynet.trash(msg, sz)
	end
end
SOCKET.on_message = message;

-- socket 一个客户端连接断开
local function disconnect(fd)
    if close_fd(fd) then return end
	skynet.send(watchdog, "lua", "socket", "close", fd);
end
SOCKET.on_disconnect = disconnect;

-- socket 一个客户端连接出错
local function error(fd, msg)
    if close_fd(fd) then return end
	skynet.send(watchdog, "lua", "socket", "error", fd, msg);
end
SOCKET.on_error = error;

-- socket 一个客户端连接警告
local function warning(fd, size)
    skynet.send(watchdog, "lua", "socket", "warning", fd, size)
end
SOCKET.on_warning = warning;
---------- socket 处理 ----------


function gate.forward(source, fd, client, address)
    -- log.debug("[gate] forward ->%s, %s, %s, %s", source, fd, client, address);  
	local c = assert(connection[fd])
	unforward(c)
	c.client = client or 0
	c.agent = address or source
	forwarding[c.agent] = c
	socket.start(fd)
end

function gate.accept(source, fd)
	local c = assert(connection[fd])
	unforward(c)
	socket.start(fd)
end

function gate.kick(source, fd)
	close_fd(fd);
end

-- 启动gate
function gate.start(source, conf)
    table.merge({gateconf, conf});

    -- 缓存watchdog
    watchdog = gateconf.watchdog or source;
    login = gateconf.login;
    max_client = gateconf.max_client or 1024;

    -- 注册gate服务名字
    local servicename = "."..(gateconf.name or "gateservice");
    skynet.register(servicename);

    local host = gateconf.host or "0.0.0.0";
    local port = assert(tonumber(gateconf.port), "gate service invalid listen port");

    log.info("gate service listen at addr -> %s:%d", host, port);
    socket.register(SOCKET);

    -- ipv4
    socket_ipv4 = socket.listen(host, port);
    socket.start(socket_ipv4);
    
    -- ipv6
    socket_ipv6 = socket.listen("::", port);
    socket.start(socket_ipv6);

    -- 启动完毕注册到login
    skynet.send(login, "lua", "register_gate", {        
        node = nil,
        addr = skynet.self(),
        name = "gate1",
        host = skynet.getenv 'gate_host' or "127.0.0.1",
        port = gateconf.port,
        watchdog = watchdog,
    });
end

-- 关闭gate
function gate.close()
    if socket_ipv4 then
        socketdriver.close(socket_ipv4)
        socket_ipv4 = nil;
    end
    if socket_ipv6 then
        socketdriver.close(socket_ipv6)
        socket_ipv6 = nil;
    end
end

-- 错误处理
function on_cmd_err(msg)
    log.error(msg);
end

-- gate初始化
function gate.init()
    skynet.dispatch("lua", function(session, source, cmd, ...)
        local f = assert(gate[cmd], "gateservice cmd=>"..cmd.." not found");
        local function ret(ok, ...)
            if session ~= SEND_SESSION_NO_RET then
                skynet.retpack(...)
            end
        end
        ret(xpcall(f, on_cmd_err, source, ...));
    end)
end

skynet.register_protocol {
    name = "client",
    id = skynet.PTYPE_CLIENT,
}
skynet.start(gate.init);

return gate;
